/*
 * Copyright (c) 2001-2008 Caucho Technology, Inc.  All rights reserved.
 *
 * The Apache Software License, Version 1.1
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Scott Ferguson
 */

package com.alibaba.com.caucho.hessian.io;

import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.*;

/**
 * Input stream for Hessian requests.
 *
 * <p>
 * HessianInput is unbuffered, so any client needs to provide its own buffering.
 *
 * <pre>
 * InputStream is = ...; // from http connection
 * HessianInput in = new HessianInput(is);
 * String value;
 * 
 * in.startReply();         // read reply header
 * value = in.readString(); // read string value
 * in.completeReply();      // read reply footer
 * </pre>
 */
public class Hessian2Input extends AbstractHessianInput implements
		Hessian2Constants {
	private static final Logger log = Logger.getLogger(Hessian2Input.class
			.getName());

	private static final double D_256 = 1.0 / 256.0;
	private static final int END_OF_DATA = -2;

	private static Field _detailMessageField;

	private static final int SIZE = 256;
	private static final int GAP = 16;

	// factory for deserializing objects in the input stream
	protected SerializerFactory _serializerFactory;

	private static boolean _isCloseStreamOnClose;

	protected ArrayList _refs;
	protected ArrayList _classDefs;
	protected ArrayList _types;

	// the underlying input stream
	private InputStream _is;
	private final byte[] _buffer = new byte[SIZE];

	// a peek character
	private int _offset;
	private int _length;

	// true for streaming data
	private boolean _isStreaming;

	// the method for a call
	private String _method;
	private int _argLength;

	private Reader _chunkReader;
	private InputStream _chunkInputStream;

	private Throwable _replyFault;

	private StringBuffer _sbuf = new StringBuffer();

	// true if this is the last chunk
	private boolean _isLastChunk;
	// the chunk length
	private int _chunkLength;

	/**
	 * Creates a new Hessian input stream, initialized with an underlying input
	 * stream.
	 *
	 * @param is
	 *            the underlying input stream.
	 */
	public Hessian2Input(InputStream is) {
		_is = is;
	}

	/**
	 * Sets the serializer factory.
	 */
	public void setSerializerFactory(SerializerFactory factory) {
		_serializerFactory = factory;
	}

	/**
	 * Gets the serializer factory.
	 */
	public SerializerFactory getSerializerFactory() {
		return _serializerFactory;
	}

	/**
	 * Gets the serializer factory, creating a default if necessary.
	 */
	public final SerializerFactory findSerializerFactory() {
		SerializerFactory factory = _serializerFactory;

		if (factory == null)
			_serializerFactory = factory = new SerializerFactory();

		return factory;
	}

	public void setCloseStreamOnClose(boolean isClose) {
		_isCloseStreamOnClose = isClose;
	}

	public boolean isCloseStreamOnClose() {
		return _isCloseStreamOnClose;
	}

	/**
	 * Returns the calls method
	 */
	public String getMethod() {
		return _method;
	}

	/**
	 * Returns any reply fault.
	 */
	public Throwable getReplyFault() {
		return _replyFault;
	}

	/**
	 * Starts reading the call
	 *
	 * <pre>
	 * c major minor
	 * </pre>
	 */
	public int readCall() throws IOException {
		int tag = read();

		if (tag != 'C')
			throw error("expected hessian call ('C') at " + codeName(tag));

		return 0;
	}

	/**
	 * Starts reading the envelope
	 *
	 * <pre>
	 * E major minor
	 * </pre>
	 */
	public int readEnvelope() throws IOException {
		int tag = read();
		int version = 0;

		if (tag == 'H') {
			int major = read();
			int minor = read();

			version = (major << 16) + minor;

			tag = read();
		}

		if (tag != 'E')
			throw error("expected hessian Envelope ('E') at " + codeName(tag));

		return version;
	}

	/**
	 * Completes reading the envelope
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * Z
	 * </pre>
	 */
	public void completeEnvelope() throws IOException {
		int tag = read();

		if (tag != 'Z')
			error("expected end of envelope at " + codeName(tag));
	}

	/**
	 * Starts reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * string
	 * </pre>
	 */
	public String readMethod() throws IOException {
		_method = readString();

		return _method;
	}

	/**
	 * Returns the number of method arguments
	 *
	 * <pre>
	 * int
	 * </pre>
	 */
	@Override
	public int readMethodArgLength() throws IOException {
		return readInt();
	}

	/**
	 * Starts reading the call, including the headers.
	 *
	 * <p>
	 * The call expects the following protocol data
	 *
	 * <pre>
	 * c major minor
	 * m b16 b8 method
	 * </pre>
	 */
	public void startCall() throws IOException {
		readCall();

		readMethod();
	}

	/**
	 * Completes reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * </pre>
	 */
	public void completeCall() throws IOException {
	}

	/**
	 * Reads a reply as an object. If the reply has a fault, throws the
	 * exception.
	 */
	@Override
	public Object readReply(Class expectedClass) throws Throwable {
		int tag = read();

		if (tag == 'R')
			return readObject(expectedClass);
		else if (tag == 'F') {
			HashMap map = (HashMap) readObject(HashMap.class);

			throw prepareFault(map);
		} else {
			StringBuilder sb = new StringBuilder();
			sb.append((char) tag);

			try {
				int ch;

				while ((ch = read()) >= 0) {
					sb.append((char) ch);
				}
			} catch (IOException e) {
				log.log(Level.FINE, e.toString(), e);
			}

			throw error("expected hessian reply at " + codeName(tag) + "\n"
					+ sb);
		}
	}

	/**
	 * Starts reading the reply
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * r
	 * </pre>
	 */
	public void startReply() throws Throwable {
		// XXX: for variable length (?)

		readReply(Object.class);
	}

	/**
	 * Prepares the fault.
	 */
	private Throwable prepareFault(HashMap fault) throws IOException {
		Object detail = fault.get("detail");
		String message = (String) fault.get("message");

		if (detail instanceof Throwable) {
			_replyFault = (Throwable) detail;

			if (message != null && _detailMessageField != null) {
				try {
					_detailMessageField.set(_replyFault, message);
				} catch (Throwable e) {
				}
			}

			return _replyFault;
		}

		else {
			String code = (String) fault.get("code");

			_replyFault = new HessianServiceException(message, code, detail);

			return _replyFault;
		}
	}

	/**
	 * Completes reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * z
	 * </pre>
	 */
	public void completeReply() throws IOException {
	}

	/**
	 * Completes reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * z
	 * </pre>
	 */
	public void completeValueReply() throws IOException {
		int tag = read();

		if (tag != 'Z')
			error("expected end of reply at " + codeName(tag));
	}

	/**
	 * Reads a header, returning null if there are no headers.
	 *
	 * <pre>
	 * H b16 b8 value
	 * </pre>
	 */
	public String readHeader() throws IOException {
		return null;
	}

	/**
	 * Starts reading the message
	 *
	 * <pre>
	 * p major minor
	 * </pre>
	 */
	public int startMessage() throws IOException {
		int tag = read();

		if (tag == 'p')
			_isStreaming = false;
		else if (tag == 'P')
			_isStreaming = true;
		else
			throw error("expected Hessian message ('p') at " + codeName(tag));

		int major = read();
		int minor = read();

		return (major << 16) + minor;
	}

	/**
	 * Completes reading the message
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * z
	 * </pre>
	 */
	public void completeMessage() throws IOException {
		int tag = read();

		if (tag != 'Z')
			error("expected end of message at " + codeName(tag));
	}

	/**
	 * Reads a null
	 *
	 * <pre>
	 * N
	 * </pre>
	 */
	public void readNull() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return;

		default:
			throw expect("null", tag);
		}
	}
	
	public final static byte DT_BOOL	= 0;
	public final static byte DT_BYTE = 1;
	public final static byte DT_SHORT = 2;
	public final static byte DT_INT	= 3;
	public final static byte DT_LONG	= 4;
	public final static byte DT_DOUBLE = 5;
	public final static byte DT_FLOAT = 6;
	
	public final static byte DT_BYTES = 10;
	public final static byte DT_STRING = 11;
	public final static byte DT_DATE = 12;
	
	public final static byte DT_OBJECT = 20;
	
	public final static byte DT_NULL = 21;
	
	public byte getDataType() throws IOException{
		int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
		_offset--;
		
		if(tag <= 0x1f) return DT_STRING;
		
		if(tag == 0x4e) return DT_NULL;
		
		if(tag == 0x54 || tag == 0x46) return DT_BOOL;
		
		
		if(tag == 0xc8 || (tag >= 0xc0 && tag <= 0xcf))
			return DT_BYTE;
		
		if(tag == 0xd4 || (tag >= 0xd0 &&tag < 0xd7))
			return DT_SHORT;
		
		if(tag == 0x49 || tag == 0x59 || (tag >= 0x80 & tag <= 0xbf)) return DT_INT;
		
		if((tag >= 0xd8 && tag <= 0xff)
				|| (tag >= 0x38 && tag <= 0x3f)
				|| tag == 0x4c) return DT_LONG;
		
		if((tag >= 0x5b && tag <= 0x5f) || tag == 'D') return DT_DOUBLE;
		if(tag == 0x4a ||  tag == 0x4b) return DT_DATE;
		
		if(tag == 0x53 || tag == 0x52 || (tag >= 0x30 && tag <=33) || tag == 0x45) return DT_STRING;
		
		if(tag == 0x42 || tag == 0x41 || (tag >= 0x34 && tag <= 0x37) || tag ==0x47) return DT_BYTES;
		
		if(tag == 0x48
			|| tag == 0x4d || tag == 0x4f || tag == 0x51
			|| (tag >= 0x56 && tag <= 0x58)
			|| (tag >= 0x60 && tag <= 0x6f)
			|| (tag >= 0x70 && tag <= 0x7f)) return DT_OBJECT;
			
		return (byte) 0xff;
	}

	/**
	 * Reads a boolean
	 *
	 * <pre>
	 * T
	 * F
	 * </pre>
	 */
	public boolean readBoolean() throws IOException {
		int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

		switch (tag) {
		case 'T':
			return true;
		case 'F':
			return false;

			// direct integer
		case 0x80:
		case 0x81:
		case 0x82:
		case 0x83:
		case 0x84:
		case 0x85:
		case 0x86:
		case 0x87:
		case 0x88:
		case 0x89:
		case 0x8a:
		case 0x8b:
		case 0x8c:
		case 0x8d:
		case 0x8e:
		case 0x8f:

		case 0x90:
		case 0x91:
		case 0x92:
		case 0x93:
		case 0x94:
		case 0x95:
		case 0x96:
		case 0x97:
		case 0x98:
		case 0x99:
		case 0x9a:
		case 0x9b:
		case 0x9c:
		case 0x9d:
		case 0x9e:
		case 0x9f:

		case 0xa0:
		case 0xa1:
		case 0xa2:
		case 0xa3:
		case 0xa4:
		case 0xa5:
		case 0xa6:
		case 0xa7:
		case 0xa8:
		case 0xa9:
		case 0xaa:
		case 0xab:
		case 0xac:
		case 0xad:
		case 0xae:
		case 0xaf:

		case 0xb0:
		case 0xb1:
		case 0xb2:
		case 0xb3:
		case 0xb4:
		case 0xb5:
		case 0xb6:
		case 0xb7:
		case 0xb8:
		case 0xb9:
		case 0xba:
		case 0xbb:
		case 0xbc:
		case 0xbd:
		case 0xbe:
		case 0xbf:
			return tag != BC_INT_ZERO;

			// INT_BYTE = 0
		case 0xc8:
			return read() != 0;

			// INT_BYTE != 0
		case 0xc0:
		case 0xc1:
		case 0xc2:
		case 0xc3:
		case 0xc4:
		case 0xc5:
		case 0xc6:
		case 0xc7:
		case 0xc9:
		case 0xca:
		case 0xcb:
		case 0xcc:
		case 0xcd:
		case 0xce:
		case 0xcf:
			read();
			return true;

			// INT_SHORT = 0
		case 0xd4:
			return (256 * read() + read()) != 0;

			// INT_SHORT != 0
		case 0xd0:
		case 0xd1:
		case 0xd2:
		case 0xd3:
		case 0xd5:
		case 0xd6:
		case 0xd7:
			read();
			read();
			return true;

		case 'I':
			return parseInt() != 0;

		case 0xd8:
		case 0xd9:
		case 0xda:
		case 0xdb:
		case 0xdc:
		case 0xdd:
		case 0xde:
		case 0xdf:

		case 0xe0:
		case 0xe1:
		case 0xe2:
		case 0xe3:
		case 0xe4:
		case 0xe5:
		case 0xe6:
		case 0xe7:
		case 0xe8:
		case 0xe9:
		case 0xea:
		case 0xeb:
		case 0xec:
		case 0xed:
		case 0xee:
		case 0xef:
			return tag != BC_LONG_ZERO;

			// LONG_BYTE = 0
		case 0xf8:
			return read() != 0;

			// LONG_BYTE != 0
		case 0xf0:
		case 0xf1:
		case 0xf2:
		case 0xf3:
		case 0xf4:
		case 0xf5:
		case 0xf6:
		case 0xf7:
		case 0xf9:
		case 0xfa:
		case 0xfb:
		case 0xfc:
		case 0xfd:
		case 0xfe:
		case 0xff:
			read();
			return true;

			// INT_SHORT = 0
		case 0x3c:
			return (256 * read() + read()) != 0;

			// INT_SHORT != 0
		case 0x38:
		case 0x39:
		case 0x3a:
		case 0x3b:
		case 0x3d:
		case 0x3e:
		case 0x3f:
			read();
			read();
			return true;

		case BC_LONG_INT:
			return (0x1000000L * read() + 0x10000L * read() + 0x100 * read() + read()) != 0;

		case 'L':
			return parseLong() != 0;

		case BC_DOUBLE_ZERO:
			return false;

		case BC_DOUBLE_ONE:
			return true;

		case BC_DOUBLE_BYTE:
			return read() != 0;

		case BC_DOUBLE_SHORT:
			return (0x100 * read() + read()) != 0;

		case BC_DOUBLE_MILL: {
			int mills = parseInt();

			return mills != 0;
		}

		case 'D':
			return parseDouble() != 0.0;

		case 'N':
			return false;

		default:
			throw expect("boolean", tag);
		}
	}

	/**
	 * Reads a short
	 *
	 * <pre>
	 * I b32 b24 b16 b8
	 * </pre>
	 */
	public short readShort() throws IOException {
		return (short) readInt();
	}

	/**
	 * Reads an integer
	 *
	 * <pre>
	 * I b32 b24 b16 b8
	 * </pre>
	 */
	public final int readInt() throws IOException {
		// int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
		int tag = read();

		switch (tag) {
		case 'N':
			return 0;

		case 'F':
			return 0;

		case 'T':
			return 1;

			// direct integer
		case 0x80:
		case 0x81:
		case 0x82:
		case 0x83:
		case 0x84:
		case 0x85:
		case 0x86:
		case 0x87:
		case 0x88:
		case 0x89:
		case 0x8a:
		case 0x8b:
		case 0x8c:
		case 0x8d:
		case 0x8e:
		case 0x8f:

		case 0x90:
		case 0x91:
		case 0x92:
		case 0x93:
		case 0x94:
		case 0x95:
		case 0x96:
		case 0x97:
		case 0x98:
		case 0x99:
		case 0x9a:
		case 0x9b:
		case 0x9c:
		case 0x9d:
		case 0x9e:
		case 0x9f:

		case 0xa0:
		case 0xa1:
		case 0xa2:
		case 0xa3:
		case 0xa4:
		case 0xa5:
		case 0xa6:
		case 0xa7:
		case 0xa8:
		case 0xa9:
		case 0xaa:
		case 0xab:
		case 0xac:
		case 0xad:
		case 0xae:
		case 0xaf:

		case 0xb0:
		case 0xb1:
		case 0xb2:
		case 0xb3:
		case 0xb4:
		case 0xb5:
		case 0xb6:
		case 0xb7:
		case 0xb8:
		case 0xb9:
		case 0xba:
		case 0xbb:
		case 0xbc:
		case 0xbd:
		case 0xbe:
		case 0xbf:
			return tag - BC_INT_ZERO;

			/* byte int */
		case 0xc0:
		case 0xc1:
		case 0xc2:
		case 0xc3:
		case 0xc4:
		case 0xc5:
		case 0xc6:
		case 0xc7:
		case 0xc8:
		case 0xc9:
		case 0xca:
		case 0xcb:
		case 0xcc:
		case 0xcd:
		case 0xce:
		case 0xcf:
			return ((tag - BC_INT_BYTE_ZERO) << 8) + read();

			/* short int */
		case 0xd0:
		case 0xd1:
		case 0xd2:
		case 0xd3:
		case 0xd4:
		case 0xd5:
		case 0xd6:
		case 0xd7:
			return ((tag - BC_INT_SHORT_ZERO) << 16) + 256 * read() + read();

		case 'I':
		case BC_LONG_INT:
			return ((read() << 24) + (read() << 16) + (read() << 8) + read());

			// direct long
		case 0xd8:
		case 0xd9:
		case 0xda:
		case 0xdb:
		case 0xdc:
		case 0xdd:
		case 0xde:
		case 0xdf:

		case 0xe0:
		case 0xe1:
		case 0xe2:
		case 0xe3:
		case 0xe4:
		case 0xe5:
		case 0xe6:
		case 0xe7:
		case 0xe8:
		case 0xe9:
		case 0xea:
		case 0xeb:
		case 0xec:
		case 0xed:
		case 0xee:
		case 0xef:
			return tag - BC_LONG_ZERO;

			/* byte long */
		case 0xf0:
		case 0xf1:
		case 0xf2:
		case 0xf3:
		case 0xf4:
		case 0xf5:
		case 0xf6:
		case 0xf7:
		case 0xf8:
		case 0xf9:
		case 0xfa:
		case 0xfb:
		case 0xfc:
		case 0xfd:
		case 0xfe:
		case 0xff:
			return ((tag - BC_LONG_BYTE_ZERO) << 8) + read();

			/* short long */
		case 0x38:
		case 0x39:
		case 0x3a:
		case 0x3b:
		case 0x3c:
		case 0x3d:
		case 0x3e:
		case 0x3f:
			return ((tag - BC_LONG_SHORT_ZERO) << 16) + 256 * read() + read();

		case 'L':
			return (int) parseLong();

		case BC_DOUBLE_ZERO:
			return 0;

		case BC_DOUBLE_ONE:
			return 1;

			// case LONG_BYTE:
		case BC_DOUBLE_BYTE:
			return (byte) (_offset < _length ? _buffer[_offset++] : read());

			// case INT_SHORT:
			// case LONG_SHORT:
		case BC_DOUBLE_SHORT:
			return (short) (256 * read() + read());

		case BC_DOUBLE_MILL: {
			int mills = parseInt();

			return (int) (0.001 * mills);
		}

		case 'D':
			return (int) parseDouble();

		default:
			throw expect("integer", tag);
		}
	}

	/**
	 * Reads a long
	 *
	 * <pre>
	 * L b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	public long readLong() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return 0;

		case 'F':
			return 0;

		case 'T':
			return 1;

			// direct integer
		case 0x80:
		case 0x81:
		case 0x82:
		case 0x83:
		case 0x84:
		case 0x85:
		case 0x86:
		case 0x87:
		case 0x88:
		case 0x89:
		case 0x8a:
		case 0x8b:
		case 0x8c:
		case 0x8d:
		case 0x8e:
		case 0x8f:

		case 0x90:
		case 0x91:
		case 0x92:
		case 0x93:
		case 0x94:
		case 0x95:
		case 0x96:
		case 0x97:
		case 0x98:
		case 0x99:
		case 0x9a:
		case 0x9b:
		case 0x9c:
		case 0x9d:
		case 0x9e:
		case 0x9f:

		case 0xa0:
		case 0xa1:
		case 0xa2:
		case 0xa3:
		case 0xa4:
		case 0xa5:
		case 0xa6:
		case 0xa7:
		case 0xa8:
		case 0xa9:
		case 0xaa:
		case 0xab:
		case 0xac:
		case 0xad:
		case 0xae:
		case 0xaf:

		case 0xb0:
		case 0xb1:
		case 0xb2:
		case 0xb3:
		case 0xb4:
		case 0xb5:
		case 0xb6:
		case 0xb7:
		case 0xb8:
		case 0xb9:
		case 0xba:
		case 0xbb:
		case 0xbc:
		case 0xbd:
		case 0xbe:
		case 0xbf:
			return tag - BC_INT_ZERO;

			/* byte int */
		case 0xc0:
		case 0xc1:
		case 0xc2:
		case 0xc3:
		case 0xc4:
		case 0xc5:
		case 0xc6:
		case 0xc7:
		case 0xc8:
		case 0xc9:
		case 0xca:
		case 0xcb:
		case 0xcc:
		case 0xcd:
		case 0xce:
		case 0xcf:
			return ((tag - BC_INT_BYTE_ZERO) << 8) + read();

			/* short int */
		case 0xd0:
		case 0xd1:
		case 0xd2:
		case 0xd3:
		case 0xd4:
		case 0xd5:
		case 0xd6:
		case 0xd7:
			return ((tag - BC_INT_SHORT_ZERO) << 16) + 256 * read() + read();

			// case LONG_BYTE:
		case BC_DOUBLE_BYTE:
			return (byte) (_offset < _length ? _buffer[_offset++] : read());

			// case INT_SHORT:
			// case LONG_SHORT:
		case BC_DOUBLE_SHORT:
			return (short) (256 * read() + read());

		case 'I':
		case BC_LONG_INT:
			return parseInt();

			// direct long
		case 0xd8:
		case 0xd9:
		case 0xda:
		case 0xdb:
		case 0xdc:
		case 0xdd:
		case 0xde:
		case 0xdf:

		case 0xe0:
		case 0xe1:
		case 0xe2:
		case 0xe3:
		case 0xe4:
		case 0xe5:
		case 0xe6:
		case 0xe7:
		case 0xe8:
		case 0xe9:
		case 0xea:
		case 0xeb:
		case 0xec:
		case 0xed:
		case 0xee:
		case 0xef:
			return tag - BC_LONG_ZERO;

			/* byte long */
		case 0xf0:
		case 0xf1:
		case 0xf2:
		case 0xf3:
		case 0xf4:
		case 0xf5:
		case 0xf6:
		case 0xf7:
		case 0xf8:
		case 0xf9:
		case 0xfa:
		case 0xfb:
		case 0xfc:
		case 0xfd:
		case 0xfe:
		case 0xff:
			return ((tag - BC_LONG_BYTE_ZERO) << 8) + read();

			/* short long */
		case 0x38:
		case 0x39:
		case 0x3a:
		case 0x3b:
		case 0x3c:
		case 0x3d:
		case 0x3e:
		case 0x3f:
			return ((tag - BC_LONG_SHORT_ZERO) << 16) + 256 * read() + read();

		case 'L':
			return parseLong();

		case BC_DOUBLE_ZERO:
			return 0;

		case BC_DOUBLE_ONE:
			return 1;

		case BC_DOUBLE_MILL: {
			int mills = parseInt();

			return (long) (0.001 * mills);
		}

		case 'D':
			return (long) parseDouble();

		default:
			throw expect("long", tag);
		}
	}

	/**
	 * Reads a float
	 *
	 * <pre>
	 * D b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	public float readFloat() throws IOException {
		return (float) readDouble();
	}

	/**
	 * Reads a double
	 *
	 * <pre>
	 * D b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	public double readDouble() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return 0;

		case 'F':
			return 0;

		case 'T':
			return 1;

			// direct integer
		case 0x80:
		case 0x81:
		case 0x82:
		case 0x83:
		case 0x84:
		case 0x85:
		case 0x86:
		case 0x87:
		case 0x88:
		case 0x89:
		case 0x8a:
		case 0x8b:
		case 0x8c:
		case 0x8d:
		case 0x8e:
		case 0x8f:

		case 0x90:
		case 0x91:
		case 0x92:
		case 0x93:
		case 0x94:
		case 0x95:
		case 0x96:
		case 0x97:
		case 0x98:
		case 0x99:
		case 0x9a:
		case 0x9b:
		case 0x9c:
		case 0x9d:
		case 0x9e:
		case 0x9f:

		case 0xa0:
		case 0xa1:
		case 0xa2:
		case 0xa3:
		case 0xa4:
		case 0xa5:
		case 0xa6:
		case 0xa7:
		case 0xa8:
		case 0xa9:
		case 0xaa:
		case 0xab:
		case 0xac:
		case 0xad:
		case 0xae:
		case 0xaf:

		case 0xb0:
		case 0xb1:
		case 0xb2:
		case 0xb3:
		case 0xb4:
		case 0xb5:
		case 0xb6:
		case 0xb7:
		case 0xb8:
		case 0xb9:
		case 0xba:
		case 0xbb:
		case 0xbc:
		case 0xbd:
		case 0xbe:
		case 0xbf:
			return tag - 0x90;

			/* byte int */
		case 0xc0:
		case 0xc1:
		case 0xc2:
		case 0xc3:
		case 0xc4:
		case 0xc5:
		case 0xc6:
		case 0xc7:
		case 0xc8:
		case 0xc9:
		case 0xca:
		case 0xcb:
		case 0xcc:
		case 0xcd:
		case 0xce:
		case 0xcf:
			return ((tag - BC_INT_BYTE_ZERO) << 8) + read();

			/* short int */
		case 0xd0:
		case 0xd1:
		case 0xd2:
		case 0xd3:
		case 0xd4:
		case 0xd5:
		case 0xd6:
		case 0xd7:
			return ((tag - BC_INT_SHORT_ZERO) << 16) + 256 * read() + read();

		case 'I':
		case BC_LONG_INT:
			return parseInt();

			// direct long
		case 0xd8:
		case 0xd9:
		case 0xda:
		case 0xdb:
		case 0xdc:
		case 0xdd:
		case 0xde:
		case 0xdf:

		case 0xe0:
		case 0xe1:
		case 0xe2:
		case 0xe3:
		case 0xe4:
		case 0xe5:
		case 0xe6:
		case 0xe7:
		case 0xe8:
		case 0xe9:
		case 0xea:
		case 0xeb:
		case 0xec:
		case 0xed:
		case 0xee:
		case 0xef:
			return tag - BC_LONG_ZERO;

			/* byte long */
		case 0xf0:
		case 0xf1:
		case 0xf2:
		case 0xf3:
		case 0xf4:
		case 0xf5:
		case 0xf6:
		case 0xf7:
		case 0xf8:
		case 0xf9:
		case 0xfa:
		case 0xfb:
		case 0xfc:
		case 0xfd:
		case 0xfe:
		case 0xff:
			return ((tag - BC_LONG_BYTE_ZERO) << 8) + read();

			/* short long */
		case 0x38:
		case 0x39:
		case 0x3a:
		case 0x3b:
		case 0x3c:
		case 0x3d:
		case 0x3e:
		case 0x3f:
			return ((tag - BC_LONG_SHORT_ZERO) << 16) + 256 * read() + read();

		case 'L':
			return (double) parseLong();

		case BC_DOUBLE_ZERO:
			return 0;

		case BC_DOUBLE_ONE:
			return 1;

		case BC_DOUBLE_BYTE:
			return (byte) (_offset < _length ? _buffer[_offset++] : read());

		case BC_DOUBLE_SHORT:
			return (short) (256 * read() + read());

		case BC_DOUBLE_MILL: {
			int mills = parseInt();

			return 0.001 * mills;
		}

		case 'D':
			return parseDouble();

		default:
			throw expect("double", tag);
		}
	}

	/**
	 * Reads a date.
	 *
	 * <pre>
	 * T b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	public long readUTCDate() throws IOException {
		int tag = read();

		if (tag == BC_DATE) {
			return parseLong();
		} else if (tag == BC_DATE_MINUTE) {
			return parseInt() * 60000L;
		} else
			throw expect("date", tag);
	}

	/**
	 * Reads a byte from the stream.
	 */
	public int readChar() throws IOException {
		if (_chunkLength > 0) {
			_chunkLength--;
			if (_chunkLength == 0 && _isLastChunk)
				_chunkLength = END_OF_DATA;

			int ch = parseUTF8Char();
			return ch;
		} else if (_chunkLength == END_OF_DATA) {
			_chunkLength = 0;
			return -1;
		}

		int tag = read();

		switch (tag) {
		case 'N':
			return -1;

		case 'S':
		case BC_STRING_CHUNK:
			_isLastChunk = tag == 'S';
			_chunkLength = (read() << 8) + read();

			_chunkLength--;
			int value = parseUTF8Char();

			// special code so successive read byte won't
			// be read as a single object.
			if (_chunkLength == 0 && _isLastChunk)
				_chunkLength = END_OF_DATA;

			return value;

		default:
			throw expect("char", tag);
		}
	}

	/**
	 * Reads a byte array from the stream.
	 */
	public int readString(char[] buffer, int offset, int length)
			throws IOException {
		int readLength = 0;

		if (_chunkLength == END_OF_DATA) {
			_chunkLength = 0;
			return -1;
		} else if (_chunkLength == 0) {
			int tag = read();

			switch (tag) {
			case 'N':
				return -1;

			case 'S':
			case BC_STRING_CHUNK:
				_isLastChunk = tag == 'S';
				_chunkLength = (read() << 8) + read();
				break;

			case 0x00:
			case 0x01:
			case 0x02:
			case 0x03:
			case 0x04:
			case 0x05:
			case 0x06:
			case 0x07:
			case 0x08:
			case 0x09:
			case 0x0a:
			case 0x0b:
			case 0x0c:
			case 0x0d:
			case 0x0e:
			case 0x0f:

			case 0x10:
			case 0x11:
			case 0x12:
			case 0x13:
			case 0x14:
			case 0x15:
			case 0x16:
			case 0x17:
			case 0x18:
			case 0x19:
			case 0x1a:
			case 0x1b:
			case 0x1c:
			case 0x1d:
			case 0x1e:
			case 0x1f:
				_isLastChunk = true;
				_chunkLength = tag - 0x00;
				break;

			default:
				throw expect("string", tag);
			}
		}

		while (length > 0) {
			if (_chunkLength > 0) {
				buffer[offset++] = (char) parseUTF8Char();
				_chunkLength--;
				length--;
				readLength++;
			} else if (_isLastChunk) {
				if (readLength == 0)
					return -1;
				else {
					_chunkLength = END_OF_DATA;
					return readLength;
				}
			} else {
				int tag = read();

				switch (tag) {
				case 'S':
				case BC_STRING_CHUNK:
					_isLastChunk = tag == 'S';
					_chunkLength = (read() << 8) + read();
					break;

				default:
					throw expect("string", tag);
				}
			}
		}

		if (readLength == 0)
			return -1;
		else if (_chunkLength > 0 || !_isLastChunk)
			return readLength;
		else {
			_chunkLength = END_OF_DATA;
			return readLength;
		}
	}

	/**
	 * Reads a string
	 *
	 * <pre>
	 * S b16 b8 string value
	 * </pre>
	 */
	public String readString() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;
		case 'T':
			return "true";
		case 'F':
			return "false";

			// direct integer
		case 0x80:
		case 0x81:
		case 0x82:
		case 0x83:
		case 0x84:
		case 0x85:
		case 0x86:
		case 0x87:
		case 0x88:
		case 0x89:
		case 0x8a:
		case 0x8b:
		case 0x8c:
		case 0x8d:
		case 0x8e:
		case 0x8f:

		case 0x90:
		case 0x91:
		case 0x92:
		case 0x93:
		case 0x94:
		case 0x95:
		case 0x96:
		case 0x97:
		case 0x98:
		case 0x99:
		case 0x9a:
		case 0x9b:
		case 0x9c:
		case 0x9d:
		case 0x9e:
		case 0x9f:

		case 0xa0:
		case 0xa1:
		case 0xa2:
		case 0xa3:
		case 0xa4:
		case 0xa5:
		case 0xa6:
		case 0xa7:
		case 0xa8:
		case 0xa9:
		case 0xaa:
		case 0xab:
		case 0xac:
		case 0xad:
		case 0xae:
		case 0xaf:

		case 0xb0:
		case 0xb1:
		case 0xb2:
		case 0xb3:
		case 0xb4:
		case 0xb5:
		case 0xb6:
		case 0xb7:
		case 0xb8:
		case 0xb9:
		case 0xba:
		case 0xbb:
		case 0xbc:
		case 0xbd:
		case 0xbe:
		case 0xbf:
			return String.valueOf((tag - 0x90));

			/* byte int */
		case 0xc0:
		case 0xc1:
		case 0xc2:
		case 0xc3:
		case 0xc4:
		case 0xc5:
		case 0xc6:
		case 0xc7:
		case 0xc8:
		case 0xc9:
		case 0xca:
		case 0xcb:
		case 0xcc:
		case 0xcd:
		case 0xce:
		case 0xcf:
			return String.valueOf(((tag - BC_INT_BYTE_ZERO) << 8) + read());

			/* short int */
		case 0xd0:
		case 0xd1:
		case 0xd2:
		case 0xd3:
		case 0xd4:
		case 0xd5:
		case 0xd6:
		case 0xd7:
			return String.valueOf(((tag - BC_INT_SHORT_ZERO) << 16) + 256
					* read() + read());

		case 'I':
		case BC_LONG_INT:
			return String.valueOf(parseInt());

			// direct long
		case 0xd8:
		case 0xd9:
		case 0xda:
		case 0xdb:
		case 0xdc:
		case 0xdd:
		case 0xde:
		case 0xdf:

		case 0xe0:
		case 0xe1:
		case 0xe2:
		case 0xe3:
		case 0xe4:
		case 0xe5:
		case 0xe6:
		case 0xe7:
		case 0xe8:
		case 0xe9:
		case 0xea:
		case 0xeb:
		case 0xec:
		case 0xed:
		case 0xee:
		case 0xef:
			return String.valueOf(tag - BC_LONG_ZERO);

			/* byte long */
		case 0xf0:
		case 0xf1:
		case 0xf2:
		case 0xf3:
		case 0xf4:
		case 0xf5:
		case 0xf6:
		case 0xf7:
		case 0xf8:
		case 0xf9:
		case 0xfa:
		case 0xfb:
		case 0xfc:
		case 0xfd:
		case 0xfe:
		case 0xff:
			return String.valueOf(((tag - BC_LONG_BYTE_ZERO) << 8) + read());

			/* short long */
		case 0x38:
		case 0x39:
		case 0x3a:
		case 0x3b:
		case 0x3c:
		case 0x3d:
		case 0x3e:
		case 0x3f:
			return String.valueOf(((tag - BC_LONG_SHORT_ZERO) << 16) + 256
					* read() + read());

		case 'L':
			return String.valueOf(parseLong());

		case BC_DOUBLE_ZERO:
			return "0.0";

		case BC_DOUBLE_ONE:
			return "1.0";

		case BC_DOUBLE_BYTE:
			return String
					.valueOf((byte) (_offset < _length ? _buffer[_offset++]
							: read()));

		case BC_DOUBLE_SHORT:
			return String.valueOf(((short) (256 * read() + read())));

		case BC_DOUBLE_MILL: {
			int mills = parseInt();

			return String.valueOf(0.001 * mills);
		}

		case 'D':
			return String.valueOf(parseDouble());
		case BC_STRING_XLONG:
		{
			_chunkLength = (read() << 24) + (read() << 16) + (read() << 8)
			+ read();
			byte[] data = new byte[_chunkLength];
			for (int i = 0; i < _chunkLength; i++) {
				data[i] = (byte) read();
			}
			return new String(data, "UTF-8");
		}
		case BC_STRING_XSHORT:
		{
			_chunkLength = (read() << 8) + read();
			byte[] data = new byte[_chunkLength];
			for (int i = 0; i < _chunkLength; i++) {
				data[i] = (byte) read();
			}
			return new String(data, "UTF-8");
		}
		case 'S':
		case BC_STRING_CHUNK:
			_isLastChunk = tag == 'S';
			_chunkLength = (read() << 8) + read();

			_sbuf.setLength(0);
			int ch;

			while ((ch = parseChar()) >= 0)
				_sbuf.append((char) ch);

			return _sbuf.toString();

			// 0-byte string
		case 0x00:
		case 0x01:
		case 0x02:
		case 0x03:
		case 0x04:
		case 0x05:
		case 0x06:
		case 0x07:
		case 0x08:
		case 0x09:
		case 0x0a:
		case 0x0b:
		case 0x0c:
		case 0x0d:
		case 0x0e:
		case 0x0f:

		case 0x10:
		case 0x11:
		case 0x12:
		case 0x13:
		case 0x14:
		case 0x15:
		case 0x16:
		case 0x17:
		case 0x18:
		case 0x19:
		case 0x1a:
		case 0x1b:
		case 0x1c:
		case 0x1d:
		case 0x1e:
		case 0x1f:
			_isLastChunk = true;
			_chunkLength = tag - 0x00;

			_sbuf.setLength(0);

			while ((ch = parseChar()) >= 0)
				_sbuf.append((char) ch);

			return _sbuf.toString();

		case 0x30:
		case 0x31:
		case 0x32:
		case 0x33:
			_isLastChunk = true;
			_chunkLength = (tag - 0x30) * 256 + read();

			_sbuf.setLength(0);

			while ((ch = parseChar()) >= 0)
				_sbuf.append((char) ch);

			return _sbuf.toString();

		default:
			throw expect("string", tag);
		}
	}

	/**
	 * Reads a byte array
	 *
	 * <pre>
	 * B b16 b8 data value
	 * </pre>
	 */
	public byte[] readBytes() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'B':
		case BC_BINARY_CHUNK:
			_isLastChunk = tag == 'B';
			_chunkLength = (read() << 8) + read();

			ByteArrayOutputStream bos = new ByteArrayOutputStream();

			int data;
			while ((data = parseByte()) >= 0)
				bos.write(data);

			return bos.toByteArray();

		case 0x20:
		case 0x21:
		case 0x22:
		case 0x23:
		case 0x24:
		case 0x25:
		case 0x26:
		case 0x27:
		case 0x28:
		case 0x29:
		case 0x2a:
		case 0x2b:
		case 0x2c:
		case 0x2d:
		case 0x2e:
		case 0x2f: {
			_isLastChunk = true;
			_chunkLength = tag - 0x20;

			byte[] buffer = new byte[_chunkLength];

			int k = 0;
			while ((data = parseByte()) >= 0)
				buffer[k++] = (byte) data;

			return buffer;
		}

		case 0x34:
		case 0x35:
		case 0x36:
		case 0x37: {
			_isLastChunk = true;
			_chunkLength = (tag - 0x34) * 256 + read();

			byte[] buffer = new byte[_chunkLength];
			int k = 0;

			while ((data = parseByte()) >= 0) {
				buffer[k++] = (byte) data;
			}

			return buffer;
		}

		default:
			throw expect("bytes", tag);
		}
	}

	/**
	 * Reads a byte from the stream.
	 */
	public int readByte() throws IOException {
		if (_chunkLength > 0) {
			_chunkLength--;
			if (_chunkLength == 0 && _isLastChunk)
				_chunkLength = END_OF_DATA;

			return read();
		} else if (_chunkLength == END_OF_DATA) {
			_chunkLength = 0;
			return -1;
		}

		int tag = read();

		switch (tag) {
		case 'N':
			return -1;

		case 'B':
		case BC_BINARY_CHUNK:
			_isLastChunk = tag == 'B';
			_chunkLength = (read() << 8) + read();

			int value = parseByte();

			// special code so successive read byte won't
			// be read as a single object.
			if (_chunkLength == 0 && _isLastChunk)
				_chunkLength = END_OF_DATA;

			return value;

		default:
			throw expect("binary", tag);
		}
	}

	/**
	 * Reads a byte array from the stream.
	 */
	public int readBytes(byte[] buffer, int offset, int length)
			throws IOException {
		int readLength = 0;

		if (_chunkLength == END_OF_DATA) {
			_chunkLength = 0;
			return -1;
		} else if (_chunkLength == 0) {
			int tag = read();

			switch (tag) {
			case 'N':
				return -1;

			case 'B':
			case BC_BINARY_CHUNK:
				_isLastChunk = tag == 'B';
				_chunkLength = (read() << 8) + read();
				break;

			default:
				throw expect("binary", tag);
			}
		}

		while (length > 0) {
			if (_chunkLength > 0) {
				buffer[offset++] = (byte) read();
				_chunkLength--;
				length--;
				readLength++;
			} else if (_isLastChunk) {
				if (readLength == 0)
					return -1;
				else {
					_chunkLength = END_OF_DATA;
					return readLength;
				}
			} else {
				int tag = read();

				switch (tag) {
				case 'B':
				case BC_BINARY_CHUNK:
					_isLastChunk = tag == 'B';
					_chunkLength = (read() << 8) + read();
					break;

				default:
					throw expect("binary", tag);
				}
			}
		}

		if (readLength == 0)
			return -1;
		else if (_chunkLength > 0 || !_isLastChunk)
			return readLength;
		else {
			_chunkLength = END_OF_DATA;
			return readLength;
		}
	}

	/**
	 * Reads a fault.
	 */
	private HashMap readFault() throws IOException {
		HashMap map = new HashMap();

		int code = read();
		for (; code > 0 && code != 'Z'; code = read()) {
			_offset--;

			Object key = readObject();
			Object value = readObject();

			if (key != null && value != null)
				map.put(key, value);
		}

		if (code != 'Z')
			throw expect("fault", code);

		return map;
	}

	/**
	 * Reads an object from the input stream with an expected type.
	 */
	public Object readObject(Class cl) throws IOException {
		if (cl == null || cl == Object.class)
			return readObject();

		int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

		switch (tag) {
		case 'N':
			return null;

		case 'H': {
			Deserializer reader = findSerializerFactory().getDeserializer(cl);

			return reader.readMap(this);
		}

		case 'M': {
			String type = readType();

			// hessian/3bb3
			if ("".equals(type)) {
				Deserializer reader;
				reader = findSerializerFactory().getDeserializer(cl);

				return reader.readMap(this);
			} else {
				Deserializer reader;
				reader = findSerializerFactory()
						.getObjectDeserializer(type, cl);

				return reader.readMap(this);
			}
		}

		case 'C': {
			readObjectDefinition(cl);

			return readObject(cl);
		}

		case 0x60:
		case 0x61:
		case 0x62:
		case 0x63:
		case 0x64:
		case 0x65:
		case 0x66:
		case 0x67:
		case 0x68:
		case 0x69:
		case 0x6a:
		case 0x6b:
		case 0x6c:
		case 0x6d:
		case 0x6e:
		case 0x6f: {
			int ref = tag - 0x60;
			int size = _classDefs.size();

			if (ref < 0 || size <= ref)
				throw new HessianProtocolException("'" + ref
						+ "' is an unknown class definition");

			ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref);

			return readObjectInstance(cl, def);
		}

		case 'O': {
			int ref = readInt();
			int size = _classDefs.size();

			if (ref < 0 || size <= ref)
				throw new HessianProtocolException("'" + ref
						+ "' is an unknown class definition");

			ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref);

			return readObjectInstance(cl, def);
		}

		case BC_LIST_VARIABLE: {
			String type = readType();

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(type, cl);

			Object v = reader.readList(this, -1);

			return v;
		}

		case BC_LIST_FIXED: {
			String type = readType();
			int length = readInt();

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(type, cl);

			Object v = reader.readLengthList(this, length);

			return v;
		}

		case 0x70:
		case 0x71:
		case 0x72:
		case 0x73:
		case 0x74:
		case 0x75:
		case 0x76:
		case 0x77: {
			int length = tag - 0x70;

			String type = readType();

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(null, cl);

			Object v = reader.readLengthList(this, length);

			return v;
		}

		case BC_LIST_VARIABLE_UNTYPED: {
			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(null, cl);

			Object v = reader.readList(this, -1);

			return v;
		}

		case BC_LIST_FIXED_UNTYPED: {
			int length = readInt();

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(null, cl);

			Object v = reader.readLengthList(this, length);

			return v;
		}

		case 0x78:
		case 0x79:
		case 0x7a:
		case 0x7b:
		case 0x7c:
		case 0x7d:
		case 0x7e:
		case 0x7f: {
			int length = tag - 0x78;

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(null, cl);

			Object v = reader.readLengthList(this, length);

			return v;
		}

		case BC_REF: {
			int ref = readInt();

			return _refs.get(ref);
		}
		}

		if (tag >= 0)
			_offset--;

		// hessian/3b2i vs hessian/3406
		// return readObject();
		Object value = findSerializerFactory().getDeserializer(cl).readObject(
				this);
		return value;
	}

	/**
	 * Reads an arbitrary object from the input stream when the type is unknown.
	 */
	public Object readObject() throws IOException {
		int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

		switch (tag) {
		case 'N':
			return null;

		case 'T':
			return Boolean.valueOf(true);

		case 'F':
			return Boolean.valueOf(false);

			// direct integer
		case 0x80:
		case 0x81:
		case 0x82:
		case 0x83:
		case 0x84:
		case 0x85:
		case 0x86:
		case 0x87:
		case 0x88:
		case 0x89:
		case 0x8a:
		case 0x8b:
		case 0x8c:
		case 0x8d:
		case 0x8e:
		case 0x8f:

		case 0x90:
		case 0x91:
		case 0x92:
		case 0x93:
		case 0x94:
		case 0x95:
		case 0x96:
		case 0x97:
		case 0x98:
		case 0x99:
		case 0x9a:
		case 0x9b:
		case 0x9c:
		case 0x9d:
		case 0x9e:
		case 0x9f:

		case 0xa0:
		case 0xa1:
		case 0xa2:
		case 0xa3:
		case 0xa4:
		case 0xa5:
		case 0xa6:
		case 0xa7:
		case 0xa8:
		case 0xa9:
		case 0xaa:
		case 0xab:
		case 0xac:
		case 0xad:
		case 0xae:
		case 0xaf:

		case 0xb0:
		case 0xb1:
		case 0xb2:
		case 0xb3:
		case 0xb4:
		case 0xb5:
		case 0xb6:
		case 0xb7:
		case 0xb8:
		case 0xb9:
		case 0xba:
		case 0xbb:
		case 0xbc:
		case 0xbd:
		case 0xbe:
		case 0xbf:
			return Integer.valueOf(tag - BC_INT_ZERO);

			/* byte int */
		case 0xc0:
		case 0xc1:
		case 0xc2:
		case 0xc3:
		case 0xc4:
		case 0xc5:
		case 0xc6:
		case 0xc7:
		case 0xc8:
		case 0xc9:
		case 0xca:
		case 0xcb:
		case 0xcc:
		case 0xcd:
		case 0xce:
		case 0xcf:
			return Integer.valueOf(((tag - BC_INT_BYTE_ZERO) << 8) + read());

			/* short int */
		case 0xd0:
		case 0xd1:
		case 0xd2:
		case 0xd3:
		case 0xd4:
		case 0xd5:
		case 0xd6:
		case 0xd7:
			return Integer.valueOf(((tag - BC_INT_SHORT_ZERO) << 16) + 256
					* read() + read());

		case 'I':
			return Integer.valueOf(parseInt());

			// direct long
		case 0xd8:
		case 0xd9:
		case 0xda:
		case 0xdb:
		case 0xdc:
		case 0xdd:
		case 0xde:
		case 0xdf:

		case 0xe0:
		case 0xe1:
		case 0xe2:
		case 0xe3:
		case 0xe4:
		case 0xe5:
		case 0xe6:
		case 0xe7:
		case 0xe8:
		case 0xe9:
		case 0xea:
		case 0xeb:
		case 0xec:
		case 0xed:
		case 0xee:
		case 0xef:
			return Long.valueOf(tag - BC_LONG_ZERO);

			/* byte long */
		case 0xf0:
		case 0xf1:
		case 0xf2:
		case 0xf3:
		case 0xf4:
		case 0xf5:
		case 0xf6:
		case 0xf7:
		case 0xf8:
		case 0xf9:
		case 0xfa:
		case 0xfb:
		case 0xfc:
		case 0xfd:
		case 0xfe:
		case 0xff:
			return Long.valueOf(((tag - BC_LONG_BYTE_ZERO) << 8) + read());

			/* short long */
		case 0x38:
		case 0x39:
		case 0x3a:
		case 0x3b:
		case 0x3c:
		case 0x3d:
		case 0x3e:
		case 0x3f:
			return Long.valueOf(((tag - BC_LONG_SHORT_ZERO) << 16) + 256
					* read() + read());

		case BC_LONG_INT:
			return Long.valueOf(parseInt());

		case 'L':
			return Long.valueOf(parseLong());

		case BC_DOUBLE_ZERO:
			return Double.valueOf(0);

		case BC_DOUBLE_ONE:
			return Double.valueOf(1);

		case BC_DOUBLE_BYTE:
			return Double.valueOf((byte) read());

		case BC_DOUBLE_SHORT:
			return Double.valueOf((short) (256 * read() + read()));

		case BC_DOUBLE_MILL: {
			int mills = parseInt();

			return Double.valueOf(0.001 * mills);
		}

		case 'D':
			return Double.valueOf(parseDouble());

		case BC_DATE:
			return new Date(parseLong());

		case BC_DATE_MINUTE:
			return new Date(parseInt() * 60000L);

		case BC_STRING_XLONG: {
			_chunkLength = (read() << 24) + (read() << 16) + (read() << 8)
					+ read();
			byte[] data = new byte[_chunkLength];
			for (int i = 0; i < _chunkLength; i++) {
				data[i] = (byte) read();
			}
			return new String(data, "UTF-8");
		}
		case BC_STRING_XSHORT: {
			_chunkLength = (read() << 8)
					+ read();
			byte[] data = new byte[_chunkLength];
			for (int i = 0; i < _chunkLength; i++) {
				data[i] = (byte) read();
			}
			return new String(data, "UTF-8");
		}
		case BC_STRING_CHUNK:
		case 'S': {
			_isLastChunk = tag == 'S';
			_chunkLength = (read() << 8) + read();

			int data;
			_sbuf.setLength(0);

			while ((data = parseChar()) >= 0)
				_sbuf.append((char) data);

			return _sbuf.toString();
		}

		case 0x00:
		case 0x01:
		case 0x02:
		case 0x03:
		case 0x04:
		case 0x05:
		case 0x06:
		case 0x07:
		case 0x08:
		case 0x09:
		case 0x0a:
		case 0x0b:
		case 0x0c:
		case 0x0d:
		case 0x0e:
		case 0x0f:

		case 0x10:
		case 0x11:
		case 0x12:
		case 0x13:
		case 0x14:
		case 0x15:
		case 0x16:
		case 0x17:
		case 0x18:
		case 0x19:
		case 0x1a:
		case 0x1b:
		case 0x1c:
		case 0x1d:
		case 0x1e:
		case 0x1f: {
			_isLastChunk = true;
			_chunkLength = tag - 0x00;

			int data;
			_sbuf.setLength(0);

			while ((data = parseChar()) >= 0)
				_sbuf.append((char) data);

			return _sbuf.toString();
		}

		case 0x30:
		case 0x31:
		case 0x32:
		case 0x33: {
			_isLastChunk = true;
			_chunkLength = (tag - 0x30) * 256 + read();

			_sbuf.setLength(0);

			int ch;
			while ((ch = parseChar()) >= 0)
				_sbuf.append((char) ch);

			return _sbuf.toString();
		}

		case BC_BINARY_CHUNK:
		case 'B': {
			_isLastChunk = tag == 'B';
			_chunkLength = (read() << 8) + read();

			int data;
			ByteArrayOutputStream bos = new ByteArrayOutputStream();

			while ((data = parseByte()) >= 0)
				bos.write(data);

			return bos.toByteArray();
		}

		case 0x20:
		case 0x21:
		case 0x22:
		case 0x23:
		case 0x24:
		case 0x25:
		case 0x26:
		case 0x27:
		case 0x28:
		case 0x29:
		case 0x2a:
		case 0x2b:
		case 0x2c:
		case 0x2d:
		case 0x2e:
		case 0x2f: {
			_isLastChunk = true;
			int len = tag - 0x20;
			_chunkLength = 0;

			byte[] data = new byte[len];

			for (int i = 0; i < len; i++)
				data[i] = (byte) read();

			return data;
		}

		case 0x34:
		case 0x35:
		case 0x36:
		case 0x37: {
			_isLastChunk = true;
			int len = (tag - 0x34) * 256 + read();
			_chunkLength = 0;

			byte[] buffer = new byte[len];

			for (int i = 0; i < len; i++) {
				buffer[i] = (byte) read();
			}

			return buffer;
		}

		case BC_LIST_VARIABLE: {
			// variable length list
			String type = readType();

			return findSerializerFactory().readList(this, -1, type);
		}

		case BC_LIST_VARIABLE_UNTYPED: {
			return findSerializerFactory().readList(this, -1, null);
		}

		case BC_LIST_FIXED: {
			// fixed length lists
			String type = readType();
			int length = readInt();

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(type, null);

			return reader.readLengthList(this, length);
		}

		case BC_LIST_FIXED_UNTYPED: {
			// fixed length lists
			int length = readInt();

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(null, null);

			return reader.readLengthList(this, length);
		}

		// compact fixed list
		case 0x70:
		case 0x71:
		case 0x72:
		case 0x73:
		case 0x74:
		case 0x75:
		case 0x76:
		case 0x77: {
			// fixed length lists
			String type = readType();
			int length = tag - 0x70;

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(type, null);

			return reader.readLengthList(this, length);
		}

		// compact fixed untyped list
		case 0x78:
		case 0x79:
		case 0x7a:
		case 0x7b:
		case 0x7c:
		case 0x7d:
		case 0x7e:
		case 0x7f: {
			// fixed length lists
			int length = tag - 0x78;

			Deserializer reader;
			reader = findSerializerFactory().getListDeserializer(null, null);

			return reader.readLengthList(this, length);
		}

		case 'H': {
			return findSerializerFactory().readMap(this, null);
		}

		case 'M': {
			String type = readType();

			return findSerializerFactory().readMap(this, type);
		}

		case 'C': {
			readObjectDefinition(null);

			return readObject();
		}

		case 0x60:
		case 0x61:
		case 0x62:
		case 0x63:
		case 0x64:
		case 0x65:
		case 0x66:
		case 0x67:
		case 0x68:
		case 0x69:
		case 0x6a:
		case 0x6b:
		case 0x6c:
		case 0x6d:
		case 0x6e:
		case 0x6f: {
			int ref = tag - 0x60;

			if (_classDefs == null)
				throw error("No classes defined at reference '{0}'" + tag);

			ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref);

			return readObjectInstance(null, def);
		}

		case 'O': {
			int ref = readInt();

			ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref);

			return readObjectInstance(null, def);
		}

		case BC_REF: {
			int ref = readInt();

			return _refs.get(ref);
		}

		default:
			if (tag < 0)
				throw new EOFException("readObject: unexpected end of file");
			else
				throw error("readObject: unknown code " + codeName(tag));
		}
	}

	/**
	 * Reads an object definition:
	 *
	 * <pre>
	 * O string <int> (string)* <value>*
	 * </pre>
	 */
	private void readObjectDefinition(Class cl) throws IOException {
		String type = readString();
		int len = readInt();

		String[] fieldNames = new String[len];
		for (int i = 0; i < len; i++)
			fieldNames[i] = readString();

		ObjectDefinition def = new ObjectDefinition(type, fieldNames);

		if (_classDefs == null)
			_classDefs = new ArrayList();

		_classDefs.add(def);
	}

	private Object readObjectInstance(Class cl, ObjectDefinition def)
			throws IOException {
		String type = def.getType();
		String[] fieldNames = def.getFieldNames();

		if (cl != null) {
			Deserializer reader;
			reader = findSerializerFactory().getObjectDeserializer(type, cl);

			return reader.readObject(this, fieldNames);
		} else {
			return findSerializerFactory().readObject(this, type, fieldNames);
		}
	}

	private String readLenString() throws IOException {
		int len = readInt();

		_isLastChunk = true;
		_chunkLength = len;

		_sbuf.setLength(0);
		int ch;
		while ((ch = parseChar()) >= 0)
			_sbuf.append((char) ch);

		return _sbuf.toString();
	}

	private String readLenString(int len) throws IOException {
		_isLastChunk = true;
		_chunkLength = len;

		_sbuf.setLength(0);
		int ch;
		while ((ch = parseChar()) >= 0)
			_sbuf.append((char) ch);

		return _sbuf.toString();
	}

	/**
	 * Reads a remote object.
	 */
	public Object readRemote() throws IOException {
		String type = readType();
		String url = readString();

		return resolveRemote(type, url);
	}

	/**
	 * Reads a reference.
	 */
	public Object readRef() throws IOException {
		return _refs.get(parseInt());
	}

	/**
	 * Reads the start of a list.
	 */
	public int readListStart() throws IOException {
		return read();
	}

	/**
	 * Reads the start of a list.
	 */
	public int readMapStart() throws IOException {
		return read();
	}

	/**
	 * Returns true if this is the end of a list or a map.
	 */
	public boolean isEnd() throws IOException {
		int code;

		if (_offset < _length)
			code = (_buffer[_offset] & 0xff);
		else {
			code = read();

			if (code >= 0)
				_offset--;
		}

		return (code < 0 || code == 'Z');
	}

	/**
	 * Reads the end byte.
	 */
	public void readEnd() throws IOException {
		int code = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

		if (code == 'Z')
			return;
		else if (code < 0)
			throw error("unexpected end of file");
		else
			throw error("unknown code:" + codeName(code));
	}

	/**
	 * Reads the end byte.
	 */
	public void readMapEnd() throws IOException {
		int code = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

		if (code != 'Z')
			throw error("expected end of map ('Z') at '" + codeName(code) + "'");
	}

	/**
	 * Reads the end byte.
	 */
	public void readListEnd() throws IOException {
		int code = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

		if (code != 'Z')
			throw error("expected end of list ('Z') at '" + codeName(code)
					+ "'");
	}

	/**
	 * Adds a list/map reference.
	 */
	public int addRef(Object ref) {
		if (_refs == null)
			_refs = new ArrayList();

		_refs.add(ref);

		return _refs.size() - 1;
	}

	/**
	 * Adds a list/map reference.
	 */
	public void setRef(int i, Object ref) {
		_refs.set(i, ref);
	}

	/**
	 * Resets the references for streaming.
	 */
	public void resetReferences() {
		if (_refs != null)
			_refs.clear();
	}

	public Object readStreamingObject() throws IOException {
		if (_refs != null)
			_refs.clear();

		return readObject();
	}

	/**
	 * Resolves a remote object.
	 */
	public Object resolveRemote(String type, String url) throws IOException {
		HessianRemoteResolver resolver = getRemoteResolver();

		if (resolver != null)
			return resolver.lookup(type, url);
		else
			return new HessianRemote(type, url);
	}

	/**
	 * Parses a type from the stream.
	 *
	 * <pre>
	 * type ::= string
	 * type ::= int
	 * </pre>
	 */
	public String readType() throws IOException {
		int code = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
		_offset--;

		switch (code) {
		case 0x00:
		case 0x01:
		case 0x02:
		case 0x03:
		case 0x04:
		case 0x05:
		case 0x06:
		case 0x07:
		case 0x08:
		case 0x09:
		case 0x0a:
		case 0x0b:
		case 0x0c:
		case 0x0d:
		case 0x0e:
		case 0x0f:

		case 0x10:
		case 0x11:
		case 0x12:
		case 0x13:
		case 0x14:
		case 0x15:
		case 0x16:
		case 0x17:
		case 0x18:
		case 0x19:
		case 0x1a:
		case 0x1b:
		case 0x1c:
		case 0x1d:
		case 0x1e:
		case 0x1f:

		case 0x30:
		case 0x31:
		case 0x32:
		case 0x33:
		case BC_STRING_XSHORT:
		case BC_STRING_XLONG:
		case BC_STRING_CHUNK:
		case 'S': {
			String type = readString();

			if (_types == null)
				_types = new ArrayList();

			_types.add(type);

			return type;
		}
		default: {
			int ref = readInt();

			if (_types.size() <= ref)
				throw new IndexOutOfBoundsException("type ref #" + ref
						+ " is greater than the number of valid types ("
						+ _types.size() + ")");

			return (String) _types.get(ref);
		}
		}
	}

	/**
	 * Parses the length for an array
	 *
	 * <pre>
	 * l b32 b24 b16 b8
	 * </pre>
	 */
	public int readLength() throws IOException {
		throw new UnsupportedOperationException();
	}

	/**
	 * Parses a 32-bit integer value from the stream.
	 *
	 * <pre>
	 * b32 b24 b16 b8
	 * </pre>
	 */
	private int parseInt() throws IOException {
		int offset = _offset;

		if (offset + 3 < _length) {
			byte[] buffer = _buffer;

			int b32 = buffer[offset + 0] & 0xff;
			int b24 = buffer[offset + 1] & 0xff;
			int b16 = buffer[offset + 2] & 0xff;
			int b8 = buffer[offset + 3] & 0xff;

			_offset = offset + 4;

			return (b32 << 24) + (b24 << 16) + (b16 << 8) + b8;
		} else {
			int b32 = read();
			int b24 = read();
			int b16 = read();
			int b8 = read();

			return (b32 << 24) + (b24 << 16) + (b16 << 8) + b8;
		}
	}

	/**
	 * Parses a 64-bit long value from the stream.
	 *
	 * <pre>
	 * b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	private long parseLong() throws IOException {
		long b64 = read();
		long b56 = read();
		long b48 = read();
		long b40 = read();
		long b32 = read();
		long b24 = read();
		long b16 = read();
		long b8 = read();

		return ((b64 << 56) + (b56 << 48) + (b48 << 40) + (b40 << 32)
				+ (b32 << 24) + (b24 << 16) + (b16 << 8) + b8);
	}

	/**
	 * Parses a 64-bit double value from the stream.
	 *
	 * <pre>
	 * b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	private double parseDouble() throws IOException {
		long bits = parseLong();

		return Double.longBitsToDouble(bits);
	}

	org.w3c.dom.Node parseXML() throws IOException {
		throw new UnsupportedOperationException();
	}

	/**
	 * Reads a character from the underlying stream.
	 */
	private int parseChar() throws IOException {
		while (_chunkLength <= 0) {
			if (_isLastChunk)
				return -1;

			int code = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

			switch (code) {
			case BC_STRING_CHUNK:
				_isLastChunk = false;

				_chunkLength = (read() << 8) + read();
				break;

			case 'S':
				_isLastChunk = true;

				_chunkLength = (read() << 8) + read();
				break;

			case 0x00:
			case 0x01:
			case 0x02:
			case 0x03:
			case 0x04:
			case 0x05:
			case 0x06:
			case 0x07:
			case 0x08:
			case 0x09:
			case 0x0a:
			case 0x0b:
			case 0x0c:
			case 0x0d:
			case 0x0e:
			case 0x0f:

			case 0x10:
			case 0x11:
			case 0x12:
			case 0x13:
			case 0x14:
			case 0x15:
			case 0x16:
			case 0x17:
			case 0x18:
			case 0x19:
			case 0x1a:
			case 0x1b:
			case 0x1c:
			case 0x1d:
			case 0x1e:
			case 0x1f:
				_isLastChunk = true;
				_chunkLength = code - 0x00;
				break;

			// qian.lei 2010-7-21
			case 0x30:
			case 0x31:
			case 0x32:
			case 0x33:
				_isLastChunk = true;
				_chunkLength = ((code - 0x30) << 8) + read();
				break;

			default:
				throw expect("string", code);
			}

		}

		_chunkLength--;

		return parseUTF8Char();
	}

	/**
	 * Parses a single UTF8 character.
	 */
	private int parseUTF8Char() throws IOException {
		int ch = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

		if (ch < 0x80)
			return ch;
		else if ((ch & 0xe0) == 0xc0) {
			int ch1 = read();
			int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);

			return v;
		} else if ((ch & 0xf0) == 0xe0) {
			int ch1 = read();
			int ch2 = read();
			int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);

			return v;
		} else
			throw error("bad utf-8 encoding at " + codeName(ch));
	}

	/**
	 * Reads a byte from the underlying stream.
	 */
	private int parseByte() throws IOException {
		while (_chunkLength <= 0) {
			if (_isLastChunk) {
				return -1;
			}

			int code = read();

			switch (code) {
			case BC_BINARY_CHUNK:
				_isLastChunk = false;

				_chunkLength = (read() << 8) + read();
				break;

			case 'B':
				_isLastChunk = true;

				_chunkLength = (read() << 8) + read();
				break;

			case 0x20:
			case 0x21:
			case 0x22:
			case 0x23:
			case 0x24:
			case 0x25:
			case 0x26:
			case 0x27:
			case 0x28:
			case 0x29:
			case 0x2a:
			case 0x2b:
			case 0x2c:
			case 0x2d:
			case 0x2e:
			case 0x2f:
				_isLastChunk = true;

				_chunkLength = code - 0x20;
				break;

			case 0x34:
			case 0x35:
			case 0x36:
			case 0x37:
				_isLastChunk = true;
				_chunkLength = (code - 0x34) * 256 + read();
				break;

			default:
				throw expect("byte[]", code);
			}
		}

		_chunkLength--;

		return read();
	}

	/**
	 * Reads bytes based on an input stream.
	 */
	public InputStream readInputStream() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'B':
		case 'b':
			_isLastChunk = tag == 'B';
			_chunkLength = (read() << 8) + read();
			break;

		case 0x20:
		case 0x21:
		case 0x22:
		case 0x23:
		case 0x24:
		case 0x25:
		case 0x26:
		case 0x27:
		case 0x28:
		case 0x29:
		case 0x2a:
		case 0x2b:
		case 0x2c:
		case 0x2d:
		case 0x2e:
		case 0x2f:
			_isLastChunk = true;
			_chunkLength = tag - 0x20;
			break;

		default:
			throw expect("binary", tag);
		}

		return new ReadInputStream();
	}

	/**
	 * Reads bytes from the underlying stream.
	 */
	int read(byte[] buffer, int offset, int length) throws IOException {
		int readLength = 0;

		while (length > 0) {
			while (_chunkLength <= 0) {
				if (_isLastChunk)
					return readLength == 0 ? -1 : readLength;

				int code = read();

				switch (code) {
				case 'b':
					_isLastChunk = false;

					_chunkLength = (read() << 8) + read();
					break;

				case 'B':
					_isLastChunk = true;

					_chunkLength = (read() << 8) + read();
					break;

				case 0x20:
				case 0x21:
				case 0x22:
				case 0x23:
				case 0x24:
				case 0x25:
				case 0x26:
				case 0x27:
				case 0x28:
				case 0x29:
				case 0x2a:
				case 0x2b:
				case 0x2c:
				case 0x2d:
				case 0x2e:
				case 0x2f:
					_isLastChunk = true;
					_chunkLength = code - 0x20;
					break;

				default:
					throw expect("byte[]", code);
				}
			}

			int sublen = _chunkLength;
			if (length < sublen)
				sublen = length;

			if (_length <= _offset && !readBuffer())
				return -1;

			if (_length - _offset < sublen)
				sublen = _length - _offset;

			System.arraycopy(_buffer, _offset, buffer, offset, sublen);

			_offset += sublen;

			offset += sublen;
			readLength += sublen;
			length -= sublen;
			_chunkLength -= sublen;
		}

		return readLength;
	}

	/**
	 * Normally, shouldn't be called externally, but needed for QA, e.g.
	 * ejb/3b01.
	 */
	public final int read() throws IOException {
		if (_length <= _offset && !readBuffer())
			return -1;

		return _buffer[_offset++] & 0xff;
	}

	private final boolean readBuffer() throws IOException {
		byte[] buffer = _buffer;
		int offset = _offset;
		int length = _length;

		if (offset < length) {
			System.arraycopy(buffer, offset, buffer, 0, length - offset);
			offset = length - offset;
		} else
			offset = 0;

		int len = _is.read(buffer, offset, SIZE - offset);

		if (len <= 0) {
			_length = offset;
			_offset = 0;

			return offset > 0;
		}

		_length = offset + len;
		_offset = 0;

		return true;
	}

	public Reader getReader() {
		return null;
	}

	protected IOException expect(String expect, int ch) throws IOException {
		if (ch < 0)
			return error("expected " + expect + " at end of file");
		else {
			_offset--;

			try {
				Object obj = readObject();

				if (obj != null) {
					return error("expected " + expect + " at 0x"
							+ Integer.toHexString(ch & 0xff) + " "
							+ obj.getClass().getName() + " (" + obj + ")");
				} else
					return error("expected " + expect + " at 0x"
							+ Integer.toHexString(ch & 0xff) + " null");
			} catch (IOException e) {
				log.log(Level.FINE, e.toString(), e);

				return error("expected " + expect + " at 0x"
						+ Integer.toHexString(ch & 0xff));
			}
		}
	}

	protected String codeName(int ch) {
		if (ch < 0)
			return "end of file";
		else
			return "0x" + Integer.toHexString(ch & 0xff) + " (" + (char) +ch
					+ ")";
	}

	protected IOException error(String message) {
		if (_method != null)
			return new HessianProtocolException(_method + ": " + message);
		else
			return new HessianProtocolException(message);
	}

	public void close() throws IOException {
		InputStream is = _is;
		_is = null;

		if (_isCloseStreamOnClose && is != null)
			is.close();
	}

	class ReadInputStream extends InputStream {
		boolean _isClosed = false;

		public int read() throws IOException {
			if (_isClosed)
				return -1;

			int ch = parseByte();
			if (ch < 0)
				_isClosed = true;

			return ch;
		}

		public int read(byte[] buffer, int offset, int length)
				throws IOException {
			if (_isClosed)
				return -1;

			int len = Hessian2Input.this.read(buffer, offset, length);
			if (len < 0)
				_isClosed = true;

			return len;
		}

		public void close() throws IOException {
			while (read() >= 0) {
			}
		}
	};

	final static class ObjectDefinition {
		private final String _type;
		private final String[] _fields;

		ObjectDefinition(String type, String[] fields) {
			_type = type;
			_fields = fields;
		}

		String getType() {
			return _type;
		}

		String[] getFieldNames() {
			return _fields;
		}
	}

	static {
		try {
			_detailMessageField = Throwable.class
					.getDeclaredField("detailMessage");
			_detailMessageField.setAccessible(true);
		} catch (Throwable e) {
		}
	}
}
